package com.android.task6;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Set;
import java.util.UUID;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.Toast;

public class BluetoothManager {
	
	private static final String INFO_TAG = "BluetoothManager_info";
	private static final String ERROR_TAG = "Bluetooth_error";
	private static final int REQUEST_ENABLE_BT = 0;
	
	// Name for the SDP record when creating server socket
    private static final String NAME = "BluetoothContactSharing";
    // Unique UUID for this application
    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

	
	// Constants that indicate the current connection state
    public static final int STATE_NONE = 0;       // we're doing nothing
    public static final int STATE_LISTEN = 1;     // now listening for incoming connections
    public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
    public static final int STATE_CONNECTED = 3;  // now connected to a remote device

	private BluetoothAdapter mAdapter;
    private Handler mHandler;
    private AcceptThread mAcceptThread = null;
    private ConnectThread mConnectThread = null;
    private ConnectedThread mConnectedThread = null;
    

    private int mState;

    
	public BluetoothManager(Context context, Handler handler, BluetoothAdapter ba ){
		mAdapter = ba;
		mState = STATE_NONE;
		mHandler = handler;
	}
	
	public synchronized int getState(){
		return this.mState;
	}
	
	private synchronized void setState(int s){

		Log.i(INFO_TAG, "setState() " + mState + " -> " + s);

		this.mState = s;
	}
	
	//start acceptThread to begin listening (server) mode
	public synchronized void start(){
	    
	        // Cancel any thread attempting to make a connection
	        if (mConnectThread != null) {
	        	
	        	mConnectThread.cancel();
	        	mConnectThread = null;
	        	}
	        // Cancel any thread currently running a connection
	        if (mConnectedThread != null) {
	        	
	        	mConnectedThread.cancel();
	        	mConnectedThread = null;
	        	}

	        // Start the thread to listen on a BluetoothServerSocket
	        if (mAcceptThread == null) {
	        	Log.i(INFO_TAG,"Starting the AcceptThread");
	            mAcceptThread = new AcceptThread();
	            mAcceptThread.start();
	        }
	        setState(STATE_LISTEN);
	    }

	/**
     * Start the ConnectThread to initiate a connection to a remote device.
     * @param device  The BluetoothDevice to connect
     */

	public synchronized void connect(String macAdress){
		
        // Cancel any thread attempting to make a connection
        if (mState == STATE_CONNECTING) {
            
        	if (mConnectThread != null) {
            	mConnectThread.cancel();
            	mConnectThread = null;
            
            }
        }

        // Cancel any thread currently running a connection
        if (mConnectedThread != null) {
        	
        	mConnectedThread.cancel(); 
        	mConnectedThread = null;
        	
        }

        // Start the thread to connect with the given device
		BluetoothDevice device = mAdapter.getRemoteDevice(macAdress);
		
		Log.i(INFO_TAG, "connect to (line 125): " + device);
		mConnectThread = new ConnectThread(device);
        mConnectThread.start();
        
        setState(STATE_CONNECTING);
	}
	 /**
     * Start the ConnectedThread to begin managing a Bluetooth connection
     * @param socket  The BluetoothSocket on which the connection was made
     * @param device  The BluetoothDevice that has been connected
     */

	public synchronized void connected(BluetoothSocket socket, BluetoothDevice device){
		
		// Cancel the thread that completed the connection
        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}

        // Cancel any thread currently running a connection
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

        // Cancel the accept thread because we only want to connect to one device
        if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
        
        // Start the thread to manage the connection and perform transmissions
        mConnectedThread = new ConnectedThread(socket);
        mConnectedThread.start();
        setState(STATE_CONNECTED);
	}
	public synchronized void notifyUser(BluetoothSocket socket){
		Log.i(INFO_TAG, "Notify user");
		Message msg = mHandler.obtainMessage(ContactSharing.MESSAGE_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString(ContactSharing.TOAST, socket.getRemoteDevice().getAddress());
        msg.setData(bundle);
        mHandler.sendMessage(msg);
		mHandler.obtainMessage(ContactSharing.CONTACT_WRITE, socket.getRemoteDevice().getAddress()).sendToTarget();
	}
	
	  /**
     * Stop all threads
     */
    public synchronized void stop() {
    	Log.i(INFO_TAG, "stop");
        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
        if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
        setState(STATE_NONE);
    }

    
    /**
     * Write to the ConnectedThread in an unsynchronized manner
     * @param out The bytes to write
     * @see ConnectedThread#write)
     */
    public void write(Contact contact) {
        // Create temporary object
        ConnectedThread r;
        // Synchronize a copy of the ConnectedThread
        synchronized (this) {
            if (mState != STATE_CONNECTED) return;
            r = mConnectedThread;
        }
        // Perform the write unsynchronized
        r.write(contact);
    }

	
	
	private void connectionFailed(){
		setState(STATE_LISTEN);
		 // Send a failure message back to the Activity
        Message msg = mHandler.obtainMessage(ContactSharing.MESSAGE_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString(ContactSharing.TOAST, "Unable to connect device");
        msg.setData(bundle);
        mHandler.sendMessage(msg);
	}
	
	private void connectionLost(){
		setState(STATE_LISTEN);
		 // Send a failure message back to the Activity
        Message msg = mHandler.obtainMessage(ContactSharing.MESSAGE_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString(ContactSharing.TOAST, "Device connection was lost");
        msg.setData(bundle);
        mHandler.sendMessage(msg);

	}
	
	
	//connecting as a server
	private class AcceptThread extends Thread {
	    private final BluetoothServerSocket mmServerSocket;

	    public AcceptThread() {
	        // Use a temporary object that is later assigned to mmServerSocket,
	        // because mmServerSocket is final
	        BluetoothServerSocket tmp = null;
	        try {
	            // MY_UUID is the app's UUID string, also used by the client code
	            tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
	            Log.i(INFO_TAG,tmp.toString());
	        } 
	        catch (IOException e) {
	        	
	        	Log.i(INFO_TAG,"IOEXCEPTION"); 
	        	
	        }
	        mmServerSocket = tmp;
	    }

	    public void run() {
	    	Log.i(INFO_TAG, "BEGIN mAcceptThread" + this);
            setName("AcceptThread");

	        BluetoothSocket socket = null;
	        // Keep listening until exception occurs or a socket is returned
	        // Listen to the server socket if we're not connected
            while (true) {
            	Log.i(INFO_TAG, "whileLoop");
                try {
                    // This is a blocking call and will only return on a
                    // successful connection or an exception
                	
                    socket = mmServerSocket.accept();
                } catch (IOException e) {
                    Log.e(ERROR_TAG, "accept() failed", e);
                    break;
                }

                // If a connection was accepted
                if (socket != null) {
                	Log.i(INFO_TAG, "socket is set");
                    connected(socket, socket.getRemoteDevice());
                    try {
						mmServerSocket.close();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
                    break;
//                	Log.i(INFO_TAG, "connection was accepted");
//                    synchronized (BluetoothManager.this) {
//                        switch (mState) {
//                        case STATE_LISTEN:
//                        	//set some flag for user
//                        	notifyUser(socket);
//                        case STATE_CONNECTING:
//                      	  // Situation normal. Start the connected thread.
//                            connected(socket, socket.getRemoteDevice());
//                            break;
//                        case STATE_NONE:
//                        case STATE_CONNECTED:
//                            // Either not ready or already connected. Terminate new socket.
//                            try {
//                                socket.close();
//                            } catch (IOException e) {
//                                Log.e(ERROR_TAG, "Could not close unwanted socket", e);
//                            }
//                            break;
//                        }
//                    }
                }
            }

           Log.i(INFO_TAG, "END mAcceptThread");
	    }

	    /** Will cancel the listening socket, and cause the thread to finish */
	    public void cancel() {
	        try {
	            mmServerSocket.close();
	        } catch (IOException e) { }
	    }
	}
	
	//connecting as a client
	private class ConnectThread extends Thread {
	    private final BluetoothSocket mmSocket;
	    private final BluetoothDevice mmDevice;

	    public ConnectThread(BluetoothDevice device) {
	        // Use a temporary object that is later assigned to mmSocket,
	        // because mmSocket is final
	        BluetoothSocket tmp = null;
	        mmDevice = device;

	        // Get a BluetoothSocket to connect with the given BluetoothDevice
	        try {
	            // MY_UUID is the app's UUID string, also used by the server code
	            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
	        } catch (IOException e) { 
	        	
	        	  Log.e(ERROR_TAG, "create() failed", e);

	        }
	        mmSocket = tmp;
	    }

	    public void run() {
	    	 Log.i(INFO_TAG, "BEGIN mConnectThread");
	            setName("ConnectThread");

	        // Cancel discovery because it will slow down the connection
	        mAdapter.cancelDiscovery();
	        
	        try {
	        	Log.i(INFO_TAG,"325 in BTM");
	            // Connect the device through the socket. This will block
	            // until it succeeds or throws an exception^
	            mmSocket.connect();
	        } catch (IOException connectException) {
	            // Unable to connect; close the socket and get out
	        	Log.i(INFO_TAG,"Connection failed. Line 331");
	        	connectionFailed();

	            try {
	                mmSocket.close();
	            } catch (IOException closeException) { 
	            	Log.e(ERROR_TAG, "unable to close() socket during connection failure", closeException);
	            }
	            // Start the service over to restart listening mode
                BluetoothManager.this.start();
                return;

	        }

	        // Reset the ConnectThread because we're done
            synchronized (BluetoothManager.this) {
                mConnectThread = null;
            }

            // Start the connected thread
            connected(mmSocket, mmDevice);

	    }

	    /** Will cancel an in-progress connection, and close the socket */
	    public void cancel() {
	        try {
	            mmSocket.close();
	        } catch (IOException e) { }
	    }
	}
	
	 /**
     * This thread runs during a connection with a remote device.
     * It handles all incoming and outgoing transmissions.
     */
    private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final ObjectInputStream mmObjectInput;
        private final ObjectOutputStream mmObjectOutput;

        public ConnectedThread(BluetoothSocket socket) {
            Log.i(INFO_TAG, "create ConnectedThread");
            mmSocket = socket;
            ObjectInputStream objectIn = null;
            ObjectOutputStream objectOut = null;

            // Get the BluetoothSocket input and output streams
            try {
            	objectIn = new ObjectInputStream(socket.getInputStream());
            	objectOut = new ObjectOutputStream(socket.getOutputStream());
            } catch (IOException e) {
                Log.e(ERROR_TAG, "temp sockets not created", e);
            }

            mmObjectInput = objectIn;
            mmObjectOutput = objectOut;
        }

        public void run() {
            Log.i(INFO_TAG, "BEGIN mConnectedThread");

            // Keep listening to the InputStream while connected
            while (true) {
                try {
                                      
                	try {
						Contact newContact = (Contact) mmObjectInput.readObject();
						
						mHandler.obtainMessage(ContactSharing.CONTACT_READ, newContact).sendToTarget();
					} catch (ClassNotFoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

                } catch (IOException e) {
                	Log.e(ERROR_TAG, "disconnected", e);
                    connectionLost();
                    break;
                }
            }
        }

 
        public void write(Contact contact) {
            try {
            	mmObjectOutput.writeObject(contact);
            	
            } catch (IOException e) {
                Log.e(ERROR_TAG, "Exception during write", e);
            }
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(ERROR_TAG, "close() of connect socket failed", e);
            }
        }
    }


	
	


}
